跳到主要内容

数据绑定

使用视图模型将你的代码连接到编辑器中的绑定元素。

视图模型

Compose

与其他运行时不同,视图模型在 Compose API 中不作为独立对象存在。它们以 ViewModelSource 密封类的形式表示,该类构成用于创建视图模型实例的构建器模式的前半部分。有关后半部分(创建实例),请参阅视图模型实例

// 按名称获取源
val vmSource = ViewModelSource.Named("My View Model")
// 获取画板的默认源
val vmSource = ViewModelSource.DefaultForArtboard(artboard)

Legacy

// `view` 类型为 RiveAnimationView
view.setRiveResource(R.raw.my_rive_file)
val file = view.controller.file!!

// 按名称获取引用
val vm = file.getViewModelByName("My View Model")

// 按索引获取引用
for (i in 0 until file.viewModelCount) {
val indexedVM = file.getViewModelByIndex(i)
}

// 获取默认视图模型的引用
val defaultVM = file.defaultViewModelForArtboard(view.controller.activeArtboard!!)

视图模型实例

Compose

请参阅视图模型了解如何获取 ViewModelSource。有了它,你可以使用构建器模式创建 ViewModelInstanceSource(后半部分)。然后可以将该源传递给 rememberViewModelInstance,为组合的生命周期创建并记住实例。

// 来自上一节
val vmSource = ViewModelSource.Named("My View Model")

// 空白实例源
val vmiSourceBlank = ViewModelInstanceSource.Blank(vmSource)
// 或
val vmiSourceBlank = vmSource.blankInstance()

// 默认实例源
val vmiSourceDefault = ViewModelInstanceSource.Default(vmSource)
// 或
val vmiSourceDefault = vmSource.defaultInstance()

// 按名称获取实例源
val vmiSourceNamed = ViewModelInstanceSource.Named(vmSource, "My Instance")
// 或
val vmiSourceNamed = vmSource.namedInstance("My Instance")

// 完成的源现在可以与 Rive 文件一起使用来创建并记住实例
val viewModelInstance = rememberViewModelInstance(riveFile, vmiSourceNamed)

此外,你可以使用 Reference 变体从父实例中引用嵌套的视图模型实例。

val myVMI = rememberViewModelInstance(riveFile, mySource)
val referenceSource = ViewModelInstanceSource.Reference(myVMI, "Path/To/Nested VMI")
val nestedVMI = rememberViewModelInstance(riveFile, referenceSource)

Legacy

val vm = view.controller.file?.getViewModelByName("My View Model")!!

// 创建空白实例
val vmiBlank = vm.createBlankInstance()

// 创建默认实例
val vmiDefault = vm.createDefaultInstance()

// 按索引创建
for (i in 0 until vm.instanceCount) {
val vmiIndexed = vm.createInstanceFromIndex(i)
}

// 按名称创建
val vmiNamed = vm.createInstanceFromName("My Instance")

绑定

Compose

请参阅 Compose 数据绑定示例

ViewModelInstance 被传递给 Rive 可组合项时,与状态机的绑定会自动发生。

val vmiSource = ViewModelSource.Named("My View Model").namedInstance("My Instance")
val vmi = rememberViewModelInstance(riveFile, vmiSource)

Rive(
riveFile,
viewModelInstance = vmi
)

Legacy

请参阅 Legacy 数据绑定示例

view.setRiveResource(
R.raw.my_rive_file,
artboardName = "My Artboard",
)

val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")

// 将实例应用到状态机(推荐)
view.controller.stateMachines.first().viewModelInstance = vmi

// 或者,将实例应用到画板
view.controller.activeArtboard?.viewModelInstance = vmi

自动绑定

Compose

由于可组合项的性质,Compose API 中不存在自动绑定。由于它们是函数,与类相比很难从中获取值。回调需要一个空占位符来在触发前记住值,这比直接提供实例产生更多开销。

等效做法是创建一个无源的视图模型实例。这将在内部创建默认画板、该画板的默认视图模型以及该视图模型的默认实例。然后可以将其传递给 Rive 可组合项。

val vmi = rememberViewModelInstance(riveFile)
Rive(
riveFile,
viewModelInstance = vmi,
)

Legacy

view.setRiveResource(
R.raw.my_rive_file,
autoBind = true,
)

属性

列出属性

Compose

获取视图模型属性是一个挂起操作,因此需要从协程作用域(如 LaunchedEffect)中调用。

LaunchedEffect(riveFile) {
riveFile.getViewModelProperties("My View Model").forEach { property ->
Log.d("My Tag", "Property Name: ${property.name}, Type: ${property.type}")
}
}

Legacy

val vm = view.controller.file?.getViewModelByName("My View Model")!!

// 属性列表
val properties = vm.properties
assertContains(
properties,
ViewModel.Property(ViewModel.PropertyDataType.NUMBER, "My Number Property")
)

读取和写入属性

Compose

写入值

Compose API 没有显式的属性对象。相反,属性值直接在 ViewModelInstance 上使用接受路径的方法来设置。

val vmi = rememberViewModelInstance(...)
vmi.setNumberProperty("Path/To/Property", 10f)

读取值

值通过 Kotlin Flow 读取,当值发生变化时会发出最新值。你可以在 LaunchedEffect 中收集此 flow,或使用 collectAsState() 将其转换为 State(或使用 collectAsStateWithLifecycle() 仅在特定生命周期状态期间收集)。

要一次性获取最新值而不进行观察,可以使用终端操作符 first()

val vmi = rememberViewModelInstance(...)
// 收集为 State
val numberValue by vmi.numberPropertyFlow("Path/To/Property").collectAsState(initial = 0f)

Text(text = "Number value: $numberValue")

// 或收集
LaunchedEffect(vmi) {
vmi.numberPropertyFlow("Path/To/Property").collect { value ->
Log.d("Rive", "Number value changed: $value")
}

// 或一次性获取
val numberValue = vmi.numberPropertyFlow("Path/To/Property").first()
Log.d("Rive", "Current number value: $numberValue")
}

Legacy

val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")

val numberProperty = vmi.getNumberProperty("My Number Property")
// 获取
val numberValue = numberProperty.value
// 设置
numberProperty.value = 10f

嵌套属性路径

Compose

val parent = rememberViewModelInstance(riveFile, ViewModelSource.Named("Parent VM").namedInstance("Parent"))

// 使用引用
val child = rememberViewModelInstance(riveFile, ViewModelInstanceSource.Reference(parent, "Child"))
val nestedNumber = child.numberPropertyFlow("My Nested Number").collectAsState(0f)

// 或使用路径
val nestedNumber = parent.numberPropertyFlow("Child/My Nested Number").collectAsState(0f)

Legacy

val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")

val nestedNumberByChain = vmi
.getInstanceProperty("My Nested View Model")
.getInstanceProperty("My Second Nested VM")
.getNumberProperty("My Nested Number")

val nestedNumberByPath = vmi
.getNumberProperty("My Nested View Model/My Second Nested VM/My Nested Number")

可观察性

Compose

在使用 Compose API 与 Kotlin Flows 时,可观察性是默认行为。当你收集一个属性的 flow 时,每当该属性的值发生变化,你就会收到更新。

val vmi = rememberViewModelInstance(...)
val numberPropertyFlow = vmi.numberPropertyFlow("My Number Property").collectAsState(0f)

Legacy

val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")

val numberProperty = vmi.getNumberProperty("My Number Property")
// 观察
lifecycleScope.launch {
numberProperty.valueFlow.collect { value ->
Log.i("MyActivity", "Value: $value")
}
}
// 或在 Compose 中收集
val numberValue by numberProperty.valueFlow.collectAsState(0f) // 0 作为等待第一个值时的初始值

图片

Compose

请参阅 Compose 数据绑定图片示例

要设置图片属性,你需要一个 ImageAsset,可以使用字节数组通过 rememberImage 创建。下面的示例为了方便从原始资源加载到 Result 中,但你应该使用最适合你的应用的模式。

val imageBytes by produceState<Result<ByteArray>>(Result.Loading) {
value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.my_image)
.use { Result.Success(it.readBytes()) }
}
}

// `andThen` 映射 Result,仅在 Success 时调用 lambda,否则传播 Failure 和 Loading。
val image = imageBytes.andThen { bytes ->
rememberImage(riveWorker, bytes)
}

// 或合并为一条语句
val image = produceState<Result<ByteArray>>(Result.Loading) {
value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.my_image)
.use { Result.Success(it.readBytes()) }
}
}.value.andThen { bytes ->
rememberImage(riveWorker, bytes)
}

val vmi = rememberViewModelInstance(riveFile, ViewModelSource.Named("My View Model").defaultInstance())
LaunchedEffect(vmi, image) {
when(image) {
is Result.Failure -> { /* 处理加载图片失败 */ }
is Result.Loading -> { /* 如果需要,处理加载状态 */ }
is Result.Success -> {
// 设置图片属性值
vmi.setImage("Image property", image.value)
}
}
}

如果你想在图片加载完成且两者都成功后才展示 Rive 内容,可以使用 zip 便捷函数将多个 Result 对象组合在一起。

val fileAndImage = riveFile.zip(image)

when (fileAndImage) {
is Result.Failure -> { /* 处理加载文件或图片失败 */ }
is Result.Loading -> { /* 如果需要,处理加载状态 */ }
is Result.Success -> {
val (riveFile, image) = fileAndImage.value
// riveFile 和 image 都已成功加载
// 现在可以展示 Rive 内容并设置图片属性
}
}

有关图片资源的更多信息,请参阅加载资源

Legacy

// 从 assets 文件夹加载图片。
val imageBytes = context.resources.openRawResource(R.raw.my_image).use { stream ->
stream.readBytes()
}

val vmi = it.stateMachines.first().viewModelInstance!!

// 在视图模型实例中用新图片替换图片属性。
val riveImage = RiveRenderImage.fromEncoded(imageBytes)
vmi.getImageProperty("Image property").set(riveImage)

列表

Compose

请参阅 Compose 数据绑定列表示例

val mainVMI = rememberViewModelInstance(riveFile)
val newListItem = rememberViewModelInstance(riveFile, ViewModelSource.Named("My Item VM").namedInstance("My List Item"))
LaunchedEffect(mainVMI, newListItem) {
val listProperty = "My List"

// 将新项添加到列表末尾
mainVMI.appendToList(listProperty, newListItem)
// 在索引 0 处插入新项
mainVMI.insertToListAtIndex(listProperty, 0, newListItem)

// 交换索引 0 和 1 处的项
mainVMI.swapListItems(listProperty, 0, 1)

// 移除特定实例
mainVMI.removeFromList(listProperty, newListItem)
// 移除索引 0 处的项
mainVMI.removeFromListAtIndex(listProperty, 0)
}

由于列表的动态特性,你可能需要在协程中创建项,而不是提前使用 rememberViewModelInstance。请注意,将同一实例多次添加到列表中会导致它们共享状态,这可能不是期望的行为。使用以下模式根据需要创建新实例。

val mainVMI = rememberViewModelInstance(riveFile)
LaunchedEffect(mainVMI) {
val listProperty = "My List"
// ⚠️ 这必须被 `close`,这里通过 `AutoCloseable.use` 完成。
ViewModelInstance.fromFile(
riveFile,
ViewModelSource.Named("My Item VM").defaultInstance()
).use { item ->
mainVMI.insertToListAtIndex(listProperty, 0, item)
}
}

Legacy

// 获取默认视图模型实例和列表属性。
val vmi = animationView.file!!.firstArtboard.viewModelInstance!!
val listProperty = vmi.getListProperty("list")

// 为 "First" 和 "Second" 创建视图模型实例并添加到列表中。
val firstInstance = animationView.file!!.getViewModelByName("My Item VM").createInstanceFromName("First")
listProperty.add(firstInstance)

val secondInstance = animationView.file!!.getViewModelByName("My Item VM").createInstanceFromName("Second")
listProperty.add(secondInstance)

// 交换列表中的两个项。
listProperty.swap(0, 1)

// 从列表中移除两个项。
listProperty.remove(firstInstance)
listProperty.removeAt(0)

画板

Compose

请参阅 Compose 数据绑定画板示例

val vmi = rememberViewModelInstance(mainFile)
val artboard = rememberArtboard(mainFile, "My Artboard")

LaunchedEffect(vmi, artboard) {
vmi.setArtboard("My Artboard Property", artboard)
}

Legacy

// 获取默认视图模型实例和画板属性。
val vmi = animationView.file!!.firstArtboard.viewModelInstance!!
val artboardProperty = vmi.getArtboardProperty("My Artboard Property")

// 从同一文件设置画板。
val localArtboard = animationView.file!!.getArtboard("My Artboard")
artboardProperty.set(localArtboard)

// 如果需要,加载外部文件
val externalFile = File.load(context.assets, "external_file.riv")

// 从外部文件设置画板。
val externalArtboard = externalFile.getArtboard("My External Artboard")
artboardProperty.set(externalArtboard)

// 完成后清理外部文件
externalFile.dispose()

枚举

Compose

LaunchedEffect(riveFile) {
val enums = riveFile.getEnums()
Log.i("RiveEnums", "First enum name: ${enums[0].name}")
}

Legacy

val enums = view.controller.file?.enums!!

val firstEnumName = enums[0].name
val firstEnumFirstValue = enums[0].values[0]

示例

Compose

请参阅以下示例:

Legacy

请参阅数据绑定概述示例